package com.gitee.wsl.mathematics.geometry.d3.face

import com.gitee.wsl.api.Id
import com.gitee.wsl.mathematics.base.Tagged
import com.gitee.wsl.mathematics.geometry.d3.edge.Edge
import com.gitee.wsl.mathematics.geometry.d3.plane.MutablePlane
import com.gitee.wsl.mathematics.geometry.d3.plane.Plane
import com.gitee.wsl.mathematics.geometry.d3.plane.averagePlane
import com.gitee.wsl.mathematics.geometry.d3.plane.contains
import com.gitee.wsl.mathematics.geometry.d3.vertex.AnyKind
import com.gitee.wsl.mathematics.geometry.d3.vertex.MutableKind
import com.gitee.wsl.mathematics.geometry.d3.vertex.MutableVertex
import com.gitee.wsl.mathematics.geometry.d3.vertex.Vertex
import com.gitee.wsl.mathematics.geometry.d3.vertex.idString
import com.gitee.wsl.mathematics.geometry.d3.vertex.toIdOrNull
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline

interface Face : Id, Plane {
    val fvs: List<Vertex>
    val kind: FaceKind
    val isPlanar: Boolean
    val directedEdges: List<Edge> // edges are properly ordered clockwise
}

class MutableFace(
    override val id: Int,
    override val fvs: List<MutableVertex>,
    override var kind: FaceKind,
    override val directedEdges: MutableList<Edge> = ArrayList() // edges are properly ordered clockwise
) : Face, MutablePlane(fvs.averagePlane()), MutableKind<FaceKind> {
    override val isPlanar = fvs.all { it in this }

    override fun equals(other: Any?): Boolean = other is Face && id == other.id
    override fun hashCode(): Int = id
    override fun toString(): String =
        "$kind face(id=$id, [${fvs.map { it.id }.joinToString(", ")}])"
}

val Face.size: Int get() = fvs.size
fun Face.getVertex(index: Int) = fvs[index]
operator fun Face.iterator() = fvs.iterator()


@Serializable
@JvmInline
value class FaceKind(override val id: Int) : Id, AnyKind, Tagged, Comparable<FaceKind> {
    override fun compareTo(other: FaceKind): Int = id.compareTo(other.id)
    override fun toString(): String = idString(id, 'α', 'ω')
    override val tag: String get() = toString()
}

internal fun String.toFaceKindOrNull() =
    toIdOrNull('α', 'ω')?.let { FaceKind(it) }

